package inv.loader.logic.impl;

import inv.loader.converters.DateConverter;
import inv.loader.logic.LoaderException;
import inv.loader.logic.TableExport;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;


public class XMLTableExport implements TableExport {
	
	public XMLTableExport() {
		ConvertUtils.register(new DateConverter(), Date.class);
	}

	/* (non-Javadoc)
	 * @see inv.loader.logic.impl.TableExport#exportData(java.io.OutputStream, java.util.List, java.lang.String[])
	 */
	public boolean exportData(OutputStream stream, List<?> data, String[] properties) throws LoaderException{
		DocumentBuilder docBuilder;
		try {
			docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
		} catch (ParserConfigurationException e1) {
			e1.printStackTrace();
			return false;
		}
		Document doc	= docBuilder.newDocument();
		Element root	= doc.createElement("list");
		doc.appendChild(root);
		
		for(Object obj: data){
			Element el	= doc.createElement("item");
			for(String property: properties){
				Element child	= doc.createElement(property);
				Object value	= null;
				try {
					value = PropertyUtils.getProperty(obj, property);
				} catch (Exception e) {
					e.printStackTrace();
					throw new LoaderException(e.getMessage());
				} 
				child.setTextContent(ConvertUtils.convert(value));
				el.appendChild(child);
			}
			root.appendChild(el);
		}
		
		try {
			writeDocToStream(stream, doc);
		} catch (TransformerException e) {
			e.printStackTrace();
			return false;
		}
		
		return true;
	}
	
	/* (non-Javadoc)
	 * @see inv.loader.logic.impl.TableExport#exportDataWithAttributes(java.io.OutputStream, java.util.List, java.lang.String[])
	 */
	public boolean exportDataWithAttributes(OutputStream stream, List<?> data, String[] properties) throws LoaderException{
		DocumentBuilder docBuilder;
		try {
			docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
		} catch (ParserConfigurationException e1) {
			e1.printStackTrace();
			return false;
		}
		Document doc	= docBuilder.newDocument();
		Element root	= doc.createElement("list");
		doc.appendChild(root);
		
		for(Object obj: data){
			Element el	= doc.createElement("item");
			for(String property: properties){
				Object value	= null;
				try {
					value = PropertyUtils.getProperty(obj, property);
				} catch (Exception e) {
					e.printStackTrace();
					throw new LoaderException(e.getMessage());
				} 
				el.setAttribute(property, ConvertUtils.convert(value));
			}
			root.appendChild(el);
		}
		
		try {
			writeDocToStream(stream, doc);
		} catch (TransformerException e) {
			e.printStackTrace();
			throw new LoaderException(e.getMessage());
		}
		return true;
	}
	
	/* (non-Javadoc)
	 * @see inv.loader.logic.impl.TableExport#importData(java.lang.Class, java.io.InputStream)
	 */
	public <T> List<T> importData(Class<T> beanClass, InputStream stream) throws LoaderException{
		Document doc	= null;
		try {
			doc = readDocFromStream(stream);
		} catch (Exception e) {
			e.printStackTrace();
			throw new LoaderException(e.getMessage());
		} 
		
		if(doc == null)
			return null;
		
		Node rootElement	=  doc.getFirstChild();
		NodeList list		= rootElement.getChildNodes();
		List<T> returnList	= new ArrayList<T>();
		for(int i=0;i<list.getLength();i++){
			Node node	= list.item(i);
			if(node.getNodeType() != Node.ELEMENT_NODE)
				continue;
			NodeList properties	= node.getChildNodes();
			if(properties != null){
				T obj	= null;
				try {
					obj = beanClass.getConstructor().newInstance();
				} catch (Exception e) {
					e.printStackTrace();
					throw new LoaderException(e.getMessage());
				} 
				if(obj == null)
					return null;
				
				Map<String, String> propertyMap	= new HashMap<String, String>();
				boolean propertySet = false;
				for(int j=0;j<properties.getLength();j++){
					Node property = properties.item(j);
					if(property.getNodeType() == Node.ELEMENT_NODE){
						propertySet = true;
						String propertyName = property.getNodeName();
						String propertyValue = property.getTextContent();

						propertyMap.put(propertyName, propertyValue);
					}
				}
				if(propertySet){
					try {
						BeanUtils.populate(obj, propertyMap);
					} catch (Exception e) {
						e.printStackTrace();
						throw new LoaderException(e.getMessage());
					} 
					returnList.add(obj);
				}
			}
			
			// Check for attributes too
			{
				NamedNodeMap attrProperties = node.getAttributes();
				T obj	= null;
				try {
					obj = beanClass.getConstructor().newInstance();
				} catch (Exception e) {
					e.printStackTrace();
					throw new LoaderException(e.getMessage());
				} 
				if(obj == null)
					return null;
				
				boolean propertySet = false;
				for(int j=0;j<attrProperties.getLength();j++){
					Node property = attrProperties.item(j);
					if(property.getNodeType() == Node.ATTRIBUTE_NODE){
						propertySet = true;
						String propertyName = property.getNodeName();
						String propertyValue = property.getNodeValue();
						
						try {
							PropertyUtils.setProperty(obj, propertyName, propertyValue);
						} catch (Exception e) {
							e.printStackTrace();
						} 
					}
				}
				if(propertySet)
					returnList.add(obj);
			}
		}
		
		return returnList;
	}
	
	protected void writeDocToStream(OutputStream stream, Document document) throws TransformerException{
		   // Use a Transformer for output
		  TransformerFactory tFactory = TransformerFactory.newInstance();
		  Transformer transformer = tFactory.newTransformer();

		  DOMSource source = new DOMSource(document);
		  StreamResult result = new StreamResult(stream);
		  transformer.setOutputProperty(OutputKeys.INDENT, "yes");
		  transformer.transform(source, result); 
	}
	
	protected Document readDocFromStream(InputStream stream) throws ParserConfigurationException, SAXException, IOException{
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		//factory.setNamespaceAware(true);
		//factory.setValidating(true); 

		DocumentBuilder builder = factory.newDocumentBuilder();
	    return builder.parse(stream);

	}

}
